home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / C⁄C++ / Tetris Light 1.0 / source / game.c < prev    next >
Text File  |  1993-08-05  |  24KB  |  951 lines

  1. /**********************************************************************\
  2.  
  3. File:        game.c
  4.  
  5. Purpose:    This screen interface module for the Tetris program.  It is
  6.             also where the playing field is stored.  This was placed
  7.             here to simplify things if we want to port Tetris to 
  8.             a character based platform, where we could just read and
  9.             write characters to screen memory and use it to store the
  10.             grid!
  11.             
  12.  
  13. ``Tetris Light'' - a simple implementation of a Tetris game.
  14. Copyright (C) 1993 Hoylen Sue
  15.  
  16. This program is free software; you can redistribute it and/or modify
  17. it under the terms of the GNU General Public License as published by
  18. the Free Software Foundation; either version 2 of the License, or
  19. (at your option) any later version.
  20.  
  21. This program is distributed in the hope that it will be useful,
  22. but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  24. GNU General Public License for more details.
  25.  
  26. You should have received a copy of the GNU General Public License
  27. along with this program; see the file COPYING.  If not, write to the
  28. Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  29.  
  30. \**********************************************************************/
  31.  
  32. #include "local.h"
  33.  
  34. #include <Sound.h>
  35.  
  36. #include "alert.h"
  37. #include "controls.h"
  38. #include "dialutil.h"
  39. #include "env.h"
  40. #include "game.h"
  41. #include "highscore.h"
  42. #include "pstring.h"
  43. #include "resources.h"
  44. #include "tetris.h"
  45. #include "windows.h"
  46.  
  47. /*--------------------------------------------------------------------*/
  48.  
  49. /* Coordinates of where to draw things (in pixels) */
  50.  
  51. #define FIELD_TOP    3
  52. #define FIELD_LEFT    3
  53. #define CELL_SIZE    16        /* Size of cell */
  54.  
  55. #define SCORE_VALUE_LEFT    170
  56. #define SCORE_VALUE_TOP        36
  57.  
  58. #define SCORE_TITLE_LEFT    170
  59. #define SCORE_TITLE_TOP        20
  60.  
  61. #define GAME_OVER_X            170
  62. #define GAME_OVER_Y            150
  63.  
  64. /* The following two are in cell units */
  65.  
  66. #define NEXT_PIECE_OFFSET_X    (NUMBER_COLS + 1)
  67. #define NEXT_PIECE_OFFSET_Y    3
  68.  
  69. /*--------------------------------------------------------------------*/
  70.  
  71. /* This macro symbol, if defined, compiles the code to draw using
  72.    primitive colours.  This will work on all Macs, but can be left
  73.    out to increase drawing efficiency. */
  74.    
  75. #define COLOUR_GRAPHICS 1
  76.  
  77. /*--------------------------------------------------------------------*/
  78.  
  79. /* Drawing and sound resources */
  80.  
  81. static Handle hit_snd = 0;    /* if null, use SysBeep */
  82. static Handle end_snd = 0;    /* if null, use SysBeep */
  83.  
  84. static unsigned char *score_str;
  85. static unsigned char *game_over_str;
  86.  
  87. static unsigned char *pause_str;
  88. static unsigned char *continue_str;
  89.  
  90. static Pattern cell_patterns[NUMBER_BLOCK_TYPES];
  91.  
  92. /*--------------------------------------------------------------------*/
  93.  
  94. /* The playing field and next block records */
  95.  
  96. static Rect field_rect = {
  97.     FIELD_TOP, FIELD_LEFT, FIELD_TOP + CELL_SIZE * NUMBER_ROWS, 
  98.     FIELD_LEFT + CELL_SIZE * NUMBER_COLS
  99. };
  100.  
  101. static unsigned char field[NUMBER_COLS][NUMBER_ROWS];
  102. static unsigned char next_block[MAX_BLOCK_SIZE][MAX_BLOCK_SIZE];
  103.  
  104. /*--------------------------------------------------------------------*/
  105.  
  106. static Boolean game_running;
  107. static Boolean game_paused;
  108. static Boolean game_window_active;    /* Records if game window is active */
  109.  
  110. static WindowPtr game_wind;
  111. static ControlHandle game_pause_ctrlh;
  112.  
  113. static RgnHandle rgn1;
  114. static RgnHandle rgn2;
  115. static RgnHandle rgn_top_row;
  116.  
  117. /*--------------------------------------------------------------------*/
  118.  
  119. /* Game file based saving and restoring routines */
  120.  
  121. static Boolean saved_file_known = FALSE;
  122. static Str255 saved_name;
  123. static INTEGER saved_vref;
  124.  
  125. struct Save_header {
  126.     unsigned long magic;
  127.     struct Tetris_state state;
  128. };
  129.  
  130. #define SAVE_MAGIC_NUMBER    ('TLsv' | 0x80808080)
  131.  
  132. /* These locations are based on centering the dialog in the classic
  133.    sized screen.  Dialog dimensions are based on those in Inside
  134.    Macintosh I. */
  135.  
  136. static const Point SFGetFile_where = {82, 103};
  137. static const Point SFPutFile_where = {104, 119};
  138.  
  139. /*--------------------------------------------------------------------*/
  140.  
  141. /* Local prototypes */
  142.  
  143. static void game_mouseDown(Point);
  144. static void game_key(unsigned char code, unsigned char ascii);
  145. static void game_update(void);
  146. static void game_activate(void);
  147. static void game_deactivate(void);
  148.  
  149. /*--------------------------------------------------------------------*/
  150.  
  151. /* Window dispatch table for game window */
  152.  
  153. static Wind_table game_dispatch_table = {
  154.     game_mouseDown,
  155.     game_key,
  156.     game_update,
  157.     game_activate,
  158.     game_deactivate
  159. };
  160.  
  161. /*--------------------------------------------------------------------*/
  162.  
  163. static void load_string(unsigned char **str, INTEGER resid)
  164. /* Tries to load the string resource with the given `resid' and sets
  165.    the pointer *str to it.  If it cannot be found, the pointer is set
  166.    to a default string.  Used by `game_init' to simplify code. */
  167. {
  168.     static unsigned char *default_string = "\p?";
  169.     
  170.     register StringHandle hand = GetString(resid);
  171.     if (hand != 0) {
  172.         HLock(hand);
  173.         *str = *hand;
  174.     } else
  175.         *str = default_string;
  176. }
  177.  
  178. /*--------------------------------------------------------------------*/
  179.  
  180. Boolean game_init(void)
  181. /* Initialization routine for game window.  Must be called at the
  182.    beginning of the program. Returns TRUE if it failed. */
  183. {
  184.     register int p;
  185.     Rect top_row;
  186.     
  187.     /* Load block patterns and strings */
  188.     
  189.     for (p = 0; p < NUMBER_BLOCK_TYPES; p ++)
  190.         GetIndPattern(cell_patterns + p, PATTERN_ID, p + 1);
  191.     
  192.     load_string(&score_str, SCORE_STR_ID);
  193.     load_string(&game_over_str, GAME_OVER_STR_ID);
  194.     load_string(&pause_str, PAUSE_STR_ID);
  195.     load_string(&continue_str, CONTINUE_STR_ID);
  196.  
  197.     /* Load sounds only if we can play them */
  198.     
  199.     if (env_SndPlay_available()) {
  200.         hit_snd = GetResource('snd ', HIT_SND_ID);
  201.         if (hit_snd != 0)
  202.             HLock(hit_snd);
  203.         end_snd = GetResource('snd ', END_SND_ID);
  204.         if (end_snd != 0)
  205.             HLock(end_snd);
  206.     }
  207.     
  208.     /* Set up drawing variables */
  209.     
  210.     rgn1 = NewRgn();
  211.     rgn2 = NewRgn();
  212.     rgn_top_row = NewRgn();
  213.     top_row = field_rect;
  214.     top_row.bottom = top_row.top + CELL_SIZE;
  215.     RectRgn(rgn_top_row, &top_row);
  216.     
  217.     /* Create and setup window and its button */
  218.     
  219.     game_wind = GetNewWindow(GAME_WINDOW_ID, NIL, (WindowPtr) -1);
  220.     if (game_wind == 0)
  221.         return TRUE;
  222.     SetWRefCon(game_wind, (LONGINT) &game_dispatch_table);
  223.     game_window_active = FALSE;
  224.     
  225.     game_pause_ctrlh = GetNewControl(GAME_PAUSE_CNTL_ID, game_wind);
  226.     if (game_pause_ctrlh == 0)
  227.         return TRUE;
  228.     
  229.     /* Set up */
  230.     
  231.     SetPort(game_wind);
  232.     TextFont(0); /* System font */
  233.     game_running = FALSE;
  234.     game_paused = FALSE;
  235.     
  236.     return FALSE; /* Success */
  237.  
  238. /*--------------------------------------------------------------------*/
  239.  
  240. void game_begin(void)
  241. /* Show the game window.  This is a separate routine so that we can
  242.    optionally load the location information from the preferences file
  243.    before showing the window. */
  244. {
  245.     ShowWindow(game_wind);
  246. }
  247.  
  248. /*--------------------------------------------------------------------*/
  249.  
  250. void game_term(void)
  251. /* To be called at the end of the program. */
  252. {
  253.     CloseRgn(rgn1);
  254.     CloseRgn(rgn2);
  255.     CloseRgn(rgn_top_row);
  256.     DisposeWindow(game_wind);    /* Controls automatically deleted */
  257. }
  258.  
  259. /*--------------------------------------------------------------------*/
  260.  
  261. static saved_file_set(unsigned char *name, INTEGER vref)
  262. {
  263.     if (name) {
  264.         pstrcpy(saved_name, name);
  265.         saved_vref = vref;
  266.         saved_file_known = TRUE;
  267.     } else {
  268.         pstrcpy(saved_name, "\pUntitled");
  269.         saved_file_known = FALSE;
  270.     }
  271.     
  272.     SetWTitle(game_wind, saved_name);
  273. }
  274.  
  275. /*====================================================================*/
  276.  
  277. /* Internal drawing routines */
  278.  
  279. static void draw_game_over(void)
  280. /* If the game is over, the game over message is drawn, otherwise the
  281.    location where that string goes is erased.  This routine is used to
  282.    both draw and erase this message. */
  283. {
  284.     if (! game_running) {
  285.         MoveTo(GAME_OVER_X, GAME_OVER_Y);
  286.         DrawString(game_over_str);
  287.     } else {
  288.         /* Determine where that string will be drawn and erase it. */
  289.         Rect r;
  290.         FontInfo fi;
  291.         
  292.         GetFontInfo(&fi);
  293.         
  294.         r.left = GAME_OVER_X;
  295.         r.right = GAME_OVER_X + StringWidth(game_over_str);
  296.         r.top = GAME_OVER_Y - fi.ascent;
  297.         r.bottom = GAME_OVER_Y + fi.descent;
  298.         
  299.         EraseRect(&r);
  300.     }
  301. }
  302.  
  303. /*--------------------------------------------------------------------*/
  304.  
  305. static void draw_score(void)
  306. /* Draws the value of the score in the game window. */
  307. {
  308.     Str255 score_string;
  309.     Rect    bound;
  310.     
  311.     bound.left = SCORE_VALUE_LEFT;
  312.     bound.top = SCORE_VALUE_TOP - 10;
  313.     bound.right = SCORE_VALUE_LEFT + 50;
  314.     bound.bottom = SCORE_VALUE_TOP + 4;
  315.     
  316.     SetPort(game_wind);
  317.  
  318.     EraseRect(&bound);
  319.     NumToString(tetris_score(), score_string);
  320.     MoveTo(SCORE_VALUE_LEFT, SCORE_VALUE_TOP);
  321.     DrawString(score_string);
  322. }
  323.  
  324. /*--------------------------------------------------------------------*/
  325.  
  326. static void draw_cell(int x, int y, unsigned char pattern)
  327. /* Internal routine to draw a cell on the screen display. Assumes
  328.    that the drawing port has already been set up.  If the pattern is
  329.    the empty cell (pattern == 0), the cell is erased. */
  330. {
  331.     Rect cell;
  332.     
  333.     cell.left = x * CELL_SIZE + FIELD_LEFT;
  334.     cell.top = y * CELL_SIZE + FIELD_TOP;
  335.     cell.right = cell.left + CELL_SIZE;
  336.     cell.bottom = cell.top + CELL_SIZE;
  337.         
  338.     if (pattern == 0)
  339.         EraseRect(&cell);
  340.     else {
  341. #ifdef COLOUR_GRAPHICS
  342.         ForeColor(greenColor);
  343. #endif
  344.  
  345.         FillRect(&cell, cell_patterns[pattern - 1]);
  346.         FrameRect(&cell);
  347.  
  348. #ifdef COLOUR_GRAPHICS
  349.         ForeColor(blackColor);
  350. #endif
  351.     }
  352. }
  353.  
  354. /*====================================================================*/
  355.  
  356. /* Dispatch table routines */
  357.  
  358. static void game_mouseDown(Point where)
  359. /* Handler for mouseDown events in the game window. Determines if the
  360.    pause/continue control has been hit, and processes it appropriately. */
  361. {
  362.     ControlHandle ctrl;
  363.     INTEGER part;
  364.     
  365.     SetPort(game_wind);
  366.     GlobalToLocal(&where);
  367.     part = FindControl(where, game_wind, &ctrl);
  368.  
  369.     if (ctrl != 0 && TrackControl(ctrl, where, NIL) != 0) {
  370.         if (game_paused) {
  371.             tetris_pause(FALSE);
  372.             SetCTitle(game_pause_ctrlh, pause_str);
  373.             game_paused = FALSE;
  374.         } else {
  375.             tetris_pause(TRUE);
  376.             SetCTitle(game_pause_ctrlh, continue_str);
  377.             game_paused = TRUE;
  378.         }
  379.     }
  380. }
  381.  
  382. /*--------------------------------------------------------------------*/
  383.  
  384. static void game_key(unsigned char code, unsigned char ascii)
  385. /* Handler for key presses when the game window is in front.
  386.    Recognizes the four control keys and processes them appropriately. */
  387. {
  388.     if (code == ctrls.code[KEY_LEFT])
  389.         tetris_try_move(move_left);
  390.     else if (code == ctrls.code[KEY_ROT])
  391.         tetris_try_move(move_anticlockwise);
  392.     else if (code == ctrls.code[KEY_RIGHT])
  393.         tetris_try_move(move_right);
  394.     else if (code == ctrls.code[KEY_DROP])
  395.         tetris_try_move(move_drop);
  396.     else {
  397.         /* Ignore all other keys */
  398.     }
  399. }
  400.  
  401. /*--------------------------------------------------------------------*/
  402.  
  403. static void game_update(void)
  404. /* Handler for update events to the game window.  Redraws the entire
  405.    game window. */
  406. {
  407.     register int x, y;
  408.     Rect r = field_rect;
  409.  
  410.     SetPort(game_wind);
  411.     DrawControls(game_wind);    
  412.     InsetRect(&r, -1, -1);
  413. #ifdef COLOUR_GRAPHICS
  414.     ForeColor(blueColor);
  415. #endif
  416.     FrameRect(&r);            /* Border to the playing field */
  417. #ifdef COLOUR_GRAPHICS
  418.     ForeColor(blackColor);
  419. #endif
  420.  
  421.     /* Show the score */
  422.     
  423.     MoveTo(SCORE_TITLE_LEFT, SCORE_TITLE_TOP);
  424.     DrawString(score_str);
  425.     draw_score();
  426.     
  427.     /* Field contents (draw from bottom up to look nice) */
  428.     
  429.     for (y = NUMBER_ROWS - 1; y >= 0; y--)
  430.         for (x = 0; x < NUMBER_COLS; x++)
  431.             if (field[x][y] != 0)
  432.                 draw_cell(x, y, field[x][y]);
  433.                 
  434.     /* The next block */
  435.     
  436.     for (x = 0; x < 4; x++)
  437.         for (y = 0; y < 4; y++)
  438.             if (next_block[x][y])
  439.                 draw_cell(x + NEXT_PIECE_OFFSET_X, y + NEXT_PIECE_OFFSET_Y,
  440.                           next_block[x][y]);
  441.  
  442.     /* Game over text, if appropriate */
  443.     
  444.     draw_game_over();
  445. }
  446.  
  447. /*--------------------------------------------------------------------*/
  448.  
  449. static void game_activate(void)
  450. /* Handler for activate events to the game window.  Takes game out of
  451.    any pause mode that was automatically entered when it went behind
  452.    another window. */
  453. {
  454.     HiliteControl(game_pause_ctrlh, 0); /* Activate it */
  455.     if (game_running && !game_paused)
  456.         tetris_pause(FALSE);
  457.  
  458.     game_window_active = TRUE;
  459. }
  460.  
  461. /*--------------------------------------------------------------------*/
  462.  
  463. static void game_deactivate(void)
  464. /* Handler for deactivate events to the game window.  Automatically
  465.    pauses the game. */
  466. {
  467.     HiliteControl(game_pause_ctrlh, 255);
  468.     if (game_running && !game_paused)
  469.         tetris_pause(TRUE);
  470.  
  471.     game_window_active = FALSE;
  472. }
  473.  
  474. /*====================================================================*/
  475.  
  476. /* Support routines for the tetris module */
  477.  
  478. void game_del_row(int victim)
  479. /* Called by the tetris module to remove a complete row from the playing
  480.    field. It is here that sounds are played and the display and field
  481.    updated. */
  482. {
  483.     register int row, col;
  484.     Rect area;
  485.  
  486. #ifdef COLOUR_GRAPHICS
  487.     /* Draws the border of the completed row in red before playing the
  488.        sound.  Should be nice if you have a colour Mac. */
  489.        
  490.     ForeColor(redColor);
  491.  
  492.     area.top = FIELD_TOP + CELL_SIZE * victim;
  493.     area.left = FIELD_LEFT;
  494.     area.bottom = area.top + CELL_SIZE; 
  495.     area.right = FIELD_LEFT + CELL_SIZE * NUMBER_COLS;
  496.     FrameRect(&area);
  497.  
  498.     ForeColor(blackColor);
  499. #endif
  500.  
  501.     /* Play the sound */
  502.     
  503.     if (ctrls.sound_on)
  504.         if (hit_snd == 0 || SndPlay(NIL, hit_snd, FALSE) != noErr)
  505.             SysBeep(5);
  506.     
  507.     /* Delete row from field array */
  508.         
  509.     for (row = victim - 1; row >= 0; row--)
  510.         for (col = 0; col < NUMBER_COLS; col++)
  511.             field[col][row + 1] = field[col][row];
  512.             
  513.     for (col = 0; col < NUMBER_COLS; col++)
  514.         field[col][0] = 0;
  515.     
  516.     /* Display this on the screen */
  517.     
  518.     area.top = FIELD_TOP;
  519.     area.left = FIELD_LEFT;
  520.     area.bottom = FIELD_TOP + CELL_SIZE * (victim + 1); 
  521.     area.right = FIELD_LEFT + CELL_SIZE * NUMBER_COLS;
  522.  
  523.     SetPort(game_wind);
  524.     ScrollRect(&area, 0, CELL_SIZE, rgn1);
  525.  
  526.     /* Need to redraw entire field if more than just the top becomes
  527.        invalid.  This is because we do not know if more rows will be
  528.        deleted after this, and hence we may be scrolling invalid
  529.        regions around.  We can't just invalidate this rgn1 because
  530.        the next scroll will move the bad pixels from under it. */
  531.     
  532.     DiffRgn(rgn1, rgn_top_row, rgn2);
  533.     if (! EmptyRgn(rgn2))
  534.         game_update();
  535. }
  536.  
  537. /*--------------------------------------------------------------------*/
  538.  
  539. void game_set(int x, int y, unsigned char pattern)
  540. /* Called by the tetris module to set a field element. If the cell is
  541.    valid, its value is stored and it is redrawn. */
  542. {
  543.     if (x < 0 || x >= NUMBER_COLS || y < 0 || y >= NUMBER_ROWS)
  544.         return;
  545.     
  546.     field[x][y] = pattern;
  547.     SetPort(game_wind);
  548.     draw_cell(x, y, pattern);
  549. }
  550.  
  551. /*--------------------------------------------------------------------*/
  552.  
  553. unsigned char game_get(int x, int y)
  554. /* Called by the tetris module to get the value of a field element.  If
  555.    the coordinates of the cell are invalid, 0 is returned. */
  556. {
  557.     if (x < 0 || x >= NUMBER_COLS || y < 0 || y >= NUMBER_ROWS)
  558.         return 0;
  559.     else
  560.         return field[x][y];
  561. }
  562.  
  563. /*--------------------------------------------------------------------*/
  564.  
  565. void game_score_changed(void)
  566. /* Called by the tetris module to indicate that the score has changed.
  567.    This function draws the new score on the screen. */
  568. {
  569.     draw_score();
  570. }
  571.  
  572. /*--------------------------------------------------------------------*/
  573.  
  574. void game_over(void)
  575. /* Called by the tetris module to indicate the game is over.  Plays
  576.    a sound and updates the display, trying to record the score in the
  577.    high score list. */
  578. {
  579.     if (ctrls.sound_on)
  580.         if (end_snd == 0 || SndPlay(NIL, end_snd, FALSE) != noErr)
  581.             SysBeep(5);
  582.     
  583.     game_running = FALSE;
  584.     SetPort(game_wind);
  585.     draw_game_over();
  586.     
  587.     HiliteControl(game_pause_ctrlh, 255); /* make inactive */
  588.     
  589.     highscore_add(tetris_score());
  590. }
  591.  
  592. /*--------------------------------------------------------------------*/
  593.  
  594. void describe_next_begin(void)
  595. /* Called by the tetris module to indicate that it is about to describe
  596.    the cells of the next block to be played (by calling the 
  597.    `describe_next_cell' below).  We clear the local record of the next
  598.    block. */
  599. {
  600.     register int x, y;
  601.     
  602.     SetPort(game_wind);
  603.  
  604.     for (x = 0; x < 4; x++)
  605.         for (y = 0; y < 4; y++)
  606.             next_block[x][y] = 0;
  607. }
  608.  
  609. /*--------------------------------------------------------------------*/
  610.  
  611. void describe_next_cell(int x, int y, unsigned char pattern)
  612. /* Used by the tetris module to describe the cells of the next block.
  613.    Will be called between calls to `describe_next_begin' and 
  614.    `describe_next_end'. */
  615. {
  616.     if (ctrls.show_next_piece)
  617.         next_block[x][y] = pattern;
  618. }
  619.  
  620. /*--------------------------------------------------------------------*/
  621.  
  622. void describe_next_end(void)
  623. /* Used by the tetris module to indicate that it is finished describing
  624.    the next block.  Invalidates the next block's display rectangular
  625.    area so that it will be updated. */
  626. {
  627.     Rect r;
  628.     
  629.     r.left = NEXT_PIECE_OFFSET_X * CELL_SIZE + FIELD_LEFT;
  630.     r.top = NEXT_PIECE_OFFSET_Y * CELL_SIZE + FIELD_TOP;
  631.     r.right = r.left + CELL_SIZE * MAX_BLOCK_SIZE;
  632.     r.bottom = r.top + CELL_SIZE * MAX_BLOCK_SIZE;
  633.     
  634.     SetPort(game_wind);
  635.     EraseRect(&r);
  636.     InvalRect(&r);
  637. }
  638.  
  639. /*====================================================================*/
  640.  
  641. /* Other interfaces to higher modules */
  642.  
  643. void game_periodic(void)
  644. /* This routine is called as often as possible.  It calls the tetris
  645.    peridic routine as often as possible when the game is played. */
  646. {
  647.     if (game_window_active && !game_paused && game_running)
  648.         tetris_periodic();
  649. }
  650.  
  651. /*--------------------------------------------------------------------*/
  652.  
  653. void game_new(void)
  654. /* This routine starts a new game, and resets the save file name and
  655.    window title. It does not ask the user whether to save the current
  656.    game or not - it is discarded. */
  657. {
  658.     register int x, y;
  659.     
  660.     /* Clears the field and next block to be empty */
  661.     
  662.     for (y = 0; y < NUMBER_ROWS; y++)
  663.         for (x = 0; x < NUMBER_COLS; x++)
  664.             field[x][y] = 0;
  665.         
  666.     /* Reset the pause push button */
  667.     
  668.     if (game_window_active)
  669.         HiliteControl(game_pause_ctrlh, 0);
  670.     
  671.     /* Start up the game */
  672.     
  673.     tetris_start(0);
  674.     game_running = TRUE;
  675.     game_paused = FALSE;
  676.     SetCTitle(game_pause_ctrlh, pause_str);
  677.     
  678.     /* Fix up the display */
  679.     
  680.     SetPort(game_wind);
  681.     EraseRect(&field_rect);
  682.     InvalRect(&field_rect);
  683.     draw_game_over();
  684.     draw_score();
  685.  
  686.     saved_file_set(0, 0);
  687. }
  688.  
  689. /*--------------------------------------------------------------------*/
  690.  
  691. void game_save_as(void)
  692. /* Save the current game under a new file, ignoring the file it might
  693.    have come from. */
  694. {
  695.     register INTEGER erc;
  696.     INTEGER file_ref;
  697.     SFReply reply;
  698.     
  699.     /* Get the file to save to */
  700.     
  701.     SFPutFile(SFPutFile_where, "\pSave game as:", "\pUntitled", NIL, &reply);
  702.     if (reply.good) {
  703.         saved_file_set(reply.fName, reply.vRefNum);
  704.         game_save();
  705.     }
  706. }
  707.  
  708. /*--------------------------------------------------------------------*/
  709.  
  710. void game_save(void)
  711. /* Creates and writes out the game information to the file specified
  712.    in `saved_reply'. If the file name is not already known, it calls
  713.    `game_save_as' to find it first. */
  714. {
  715.     OSErr erc;
  716.     LONGINT size;
  717.     INTEGER file_ref;
  718.     struct Save_header header;
  719.     
  720.     if (! saved_file_known) {
  721.         game_save_as();
  722.         return;
  723.     }
  724.         
  725.     /* Create the file, ignore error if it says it already exists */
  726.     
  727.     erc = Create(saved_name, saved_vref, CREATOR_SIGNATURE, SAVE_FILE_SIGNATURE);
  728.     if (erc != noErr && erc != dupFNErr) {
  729.         ParamText(saved_name, 0, 0, 0);
  730.         alert_erc(1, erc);
  731.         saved_file_set(0, 0);
  732.         return;
  733.     }
  734.  
  735.     erc = FSOpen(saved_name, saved_vref, &file_ref);
  736.     if (erc != noErr) {
  737.         ParamText(saved_name, 0, 0, 0);
  738.         alert_erc(2, erc);
  739.         saved_file_set(0, 0);
  740.         return;
  741.     }
  742.     
  743.     /* Fill in the header information */
  744.     
  745.     header.magic = SAVE_MAGIC_NUMBER;
  746.     tetris_state_get(&header.state);
  747.     
  748.     /* Write out the data */
  749.     
  750.     size = sizeof(header);
  751.     erc = FSWrite(file_ref, &size, &header);
  752.     if (erc != noErr || size != sizeof(header)) {
  753.         ParamText(saved_name, 0, 0, 0);
  754.         alert_erc(4, erc);
  755.         (void) FSClose(file_ref);
  756.         saved_file_set(0, 0);
  757.         return;
  758.     }
  759.  
  760.     size = sizeof(field);
  761.     erc = FSWrite(file_ref, &size, field);
  762.     if (erc != noErr || size != sizeof(field)) {
  763.         ParamText(saved_name, 0, 0, 0);
  764.         alert_erc(4, erc);
  765.         (void) FSClose(file_ref);
  766.         saved_file_set(0, 0);
  767.         return;
  768.     }
  769.     
  770.     /* Close file */
  771.     
  772.     erc = FSClose(file_ref);
  773.     if (erc != noErr) {
  774.         ParamText(saved_name, 0, 0, 0);
  775.         alert_erc(5, erc);
  776.         saved_file_set(0, 0);
  777.         return;
  778.     }
  779. }
  780.  
  781. /*--------------------------------------------------------------------*/
  782.  
  783. void game_open(void)
  784. /* Asks the user for a game save file.  If the user supplies one, it
  785.    is loaded up as the game to be played. */
  786. {
  787.     static SFTypeList sig_list = { SAVE_FILE_SIGNATURE };
  788.     SFReply reply;
  789.     
  790.     SFGetFile(SFGetFile_where, NIL, NIL, 1, sig_list, NIL, &reply);
  791.     if (reply.good)
  792.         game_load(reply.fName, reply.vRefNum);        
  793. }
  794.  
  795. /*--------------------------------------------------------------------*/
  796.  
  797. void game_load(Str255 fname, INTEGER vref)
  798. /* Loads the game from the given file. */
  799. {
  800.     OSErr erc;
  801.     INTEGER file_ref;
  802.     LONGINT size;
  803.     struct Save_header header;
  804.     unsigned char new_field[NUMBER_COLS][NUMBER_ROWS];
  805.     register int x, y;
  806.     
  807.     /* Open the save file */
  808.     
  809.     erc = FSOpen(fname, vref, &file_ref);
  810.     if (erc != noErr) {
  811.         ParamText(fname, 0, 0, 0);
  812.         alert_erc(2, erc);
  813.         return;
  814.     }
  815.     
  816.     /* Load the game */
  817.     
  818.     size = sizeof(header);
  819.     erc = FSRead(file_ref, &size, &header);
  820.     if (erc != noErr || size != sizeof(header)) {
  821.         ParamText(fname, 0, 0, 0);
  822.         alert_erc(3, erc);
  823.         (void) FSClose(file_ref);
  824.         return;
  825.     }
  826.     
  827.     size = sizeof(new_field);
  828.     erc = FSRead(file_ref, &size, new_field);
  829.     if (erc != noErr || size != sizeof(new_field)) {
  830.         ParamText(fname, 0, 0, 0);
  831.         alert_erc(3, erc);
  832.         return;
  833.     }
  834.     
  835.     /* Close the file */
  836.     
  837.     erc = FSClose(file_ref);
  838.     if (erc != noErr) {
  839.         ParamText(fname, 0, 0, 0);
  840.         alert_erc(5, erc);
  841.         return;
  842.     }
  843.  
  844.     /* Verify header */
  845.     
  846.     if (header.magic != SAVE_MAGIC_NUMBER) {
  847.         ParamText(fname, 0, 0, 0);
  848.         alert_caution(2);
  849.         return;
  850.     }
  851.     
  852.     /* Use field values, filtering out invalid values */
  853.     
  854.     for (x = 0; x < NUMBER_COLS; x++)
  855.         for (y = 0; y < NUMBER_ROWS; y++)
  856.             field[x][y] = new_field[x][y] % (NUMBER_BLOCK_TYPES + 1);
  857.     
  858.     /* Record this file as the saved file */
  859.     
  860.     saved_file_set(fname, vref);
  861.     
  862.     /* Start play */
  863.     
  864.     tetris_start(&header.state);
  865.     game_running = TRUE;
  866.     game_paused = FALSE;
  867.     SetCTitle(game_pause_ctrlh, pause_str);
  868.  
  869.     /* Update the screen */
  870.         
  871.     SetPort(game_wind);
  872.     draw_score();
  873.     draw_game_over();
  874.     EraseRect(&field_rect);
  875.     InvalRect(&field_rect);
  876. }
  877.  
  878. /*--------------------------------------------------------------------*/
  879.  
  880. OSErr game_save_location(INTEGER pref_file)
  881. /* Saves the game controls and user settings to the given resource
  882.    file. */
  883. {
  884.     OSErr erc;
  885.     LONGINT size;
  886.     Point **handle;
  887.     Rect r;
  888.     
  889.     r = (**(*(WindowPeek)game_wind).contRgn).rgnBBox;
  890.     
  891.     handle = (Point **) GetResource(PREF_RSRC_TYPE, GAME_WPOS_PREF_ID);
  892.     if (handle != NIL && HomeResFile(handle) == pref_file) {
  893.         RmveResource(handle);
  894.         erc = ResError();
  895.         if (erc != noErr)
  896.             return erc;
  897.         
  898.         DisposHandle(handle);
  899.     }
  900.     
  901.     /* Create new resource */
  902.     
  903.     erc = PtrToHand(&r, &handle, sizeof(Rect));
  904.     if (erc != noErr)
  905.         return erc;
  906.         
  907.     AddResource(handle, PREF_RSRC_TYPE, GAME_WPOS_PREF_ID, "\p");
  908.     erc = ResError();
  909.     if (erc != noErr)
  910.         return erc;
  911.     
  912.     return noErr;
  913. }
  914.  
  915. /*--------------------------------------------------------------------*/
  916.  
  917. void game_load_location(void)
  918. /* Tries to read the window position information resource.  This is
  919.    found in the preference file, so it should be open at this stage.
  920.    If it is found and the location places the top right or the top left
  921.    corner in the desktop, it is moved to that location, otherwise it is
  922.    left alone. */
  923. {
  924.     register OSErr erc;
  925.     LONGINT size;
  926.     struct Rect **handle;
  927.     
  928.     handle = (struct Rect **) GetResource(PREF_RSRC_TYPE, GAME_WPOS_PREF_ID);
  929.     if (handle) {
  930.         Point ul, ur;
  931.         
  932.         ul.h = (*handle)->left;
  933.         ul.v = (*handle)->top;
  934.         ur.h = (*handle)->right;
  935.         ur.v = (*handle)->top;
  936.         
  937.         if (PtInRgn(ul, GrayRgn) || PtInRgn(ur, GrayRgn))
  938.             MoveWindow(game_wind, (*handle)->left, (*handle)->top, TRUE);
  939.     }
  940. }
  941.  
  942. /*--------------------------------------------------------------------*/
  943.  
  944. extern Boolean game_is_over(void)
  945. {
  946.     return (! game_running);
  947. }
  948.  
  949. /*--------------------------------------------------------------------*/
  950.